﻿#pragma once

#include "time.h"

#ifdef _WIN32
#include <Windows.h>
#endif

namespace os
{
    inline time_point_t now_time_point() noexcept
    {
#if defined(__linux__) && TIME_CLOCK_COARSE
        timespec ts;
        ::clock_gettime(CLOCK_REALTIME_COARSE, &ts);
        return std::chrono::time_point<clock_t, typename clock_t::duration>(
            std::chrono::duration_cast<typename clock_t::duration>(
                std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec)));
#endif

        return clock_t::now();
    }

    inline tm_t localtime(const time_t &time) noexcept
    {
        tm_t t;
#ifdef _WIN32
        // _WIN32: 32位和64位程序都有，且总是定义的.
        ::localtime_s(&t, &time);
#else
        ::localtime_r(&t, &time);
#endif // _WIN32
        return t;
    }

    inline tm_t localtime() noexcept
    {
        time_t now_time = ::time(nullptr);
        return localtime(now_time);
    }

    inline tm_t gmtime(const time_t &time) noexcept
    {
        tm_t t;
#ifdef _WIN32
        ::gmtime_s(&t, &time);
#else
        ::gmtime_r(&t, &time);
#endif // _WIN32
        return t;
    }

    inline tm_t gmtime() noexcept
    {
        time_t now_time = ::time(nullptr);
        return gmtime(now_time);
    }

    inline int gettimeofday(timeval *tv, timezone_t* tz/* = nullptr*/)
    {
#ifdef __linux__
        return ::gettimeofday(&tv, tz);
#elif defined(_WIN32)
        // https://stackoverflow.com/questions/10905892/equivalent-of-gettimeday-for-windows
        if (tv)
        {
            FILETIME filetime; /* 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 00:00 UTC */
            ULARGE_INTEGER x;
            ULONGLONG usec;
            static const ULONGLONG epoch_offset_us = 11644473600000000ULL; /* microseconds betweeen Jan 1,1601 and Jan 1,1970 */

#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
            GetSystemTimePreciseAsFileTime(&filetime);
#else
            GetSystemTimeAsFileTime(&filetime);
#endif
            x.LowPart = filetime.dwLowDateTime;
            x.HighPart = filetime.dwHighDateTime;
            usec = x.QuadPart / 10 - epoch_offset_us;
            tv->tv_sec = (time_t)(usec / 1000000ULL);
            tv->tv_usec = (long)(usec % 1000000ULL);
        }
        if (tz)
        {
            TIME_ZONE_INFORMATION timezone;
            GetTimeZoneInformation(&timezone);
            tz->tz_minuteswest = timezone.Bias;
            tz->tz_dsttime = 0;
        }
        return 0;
#endif        
    }

    inline milliseconds_t get_milliseconds_since_epoch()
    {
#ifdef _WIN32
        return std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::system_clock::now().time_since_epoch()).count();
#endif // _WIN32
        struct timeval tv;
        gettimeofday(&tv);
        return tv.tv_sec * 1000ull + tv.tv_usec / 1000ull;
    }

    inline seconds_t get_seconds_since_epoch()
    {
#ifdef WIN32
        return std::chrono::duration_cast<std::chrono::seconds>(
            std::chrono::system_clock::now().time_since_epoch()).count();
#endif
        timeval tv;
        gettimeofday(&tv);
        return tv.tv_sec;
    }
} // namespace os
